package com.tencent.bk.sdk.iam.util.http;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder {
    private static final Logger log = LoggerFactory.getLogger(DefaultApacheHttpClientBuilder.class);
    private final AtomicBoolean prepared;
    private int connectionRequestTimeout;
    private int connectionTimeout;
    private int soTimeout;
    private int idleConnTimeout;
    private int checkWaitTime;
    private int maxConnPerHost;
    private int maxTotalConn;
    private String userAgent;
    private HttpRequestRetryHandler httpRequestRetryHandler;
    private SSLConnectionSocketFactory sslConnectionSocketFactory;
    private PlainConnectionSocketFactory plainConnectionSocketFactory;
    private String httpProxyHost;
    private int httpProxyPort;
    private String httpProxyUsername;
    private String httpProxyPassword;
    private IdleConnectionMonitorThread idleConnectionMonitorThread;
    private CloseableHttpClient closeableHttpClient;

    private DefaultApacheHttpClientBuilder() {
        this.prepared = new AtomicBoolean(false);
        this.connectionRequestTimeout = 3000;
        this.connectionTimeout = 5000;
        this.soTimeout = 5000;
        this.idleConnTimeout = 60000;
        this.checkWaitTime = 60000;
        this.maxConnPerHost = 200;
        this.maxTotalConn = 1000;
        this.httpRequestRetryHandler = new HttpRequestRetryHandler() {
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                return false;
            }
        };
        this.sslConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
        this.plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory();
    }

    public static DefaultApacheHttpClientBuilder get() {
        return DefaultApacheHttpClientBuilder.SingletonHolder.INSTANCE;
    }

    public ApacheHttpClientBuilder httpProxyHost(String httpProxyHost) {
        this.httpProxyHost = httpProxyHost;
        return this;
    }

    public ApacheHttpClientBuilder httpProxyPort(int httpProxyPort) {
        this.httpProxyPort = httpProxyPort;
        return this;
    }

    public ApacheHttpClientBuilder httpProxyUsername(String httpProxyUsername) {
        this.httpProxyUsername = httpProxyUsername;
        return this;
    }

    public ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword) {
        this.httpProxyPassword = httpProxyPassword;
        return this;
    }

    public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory) {
        this.sslConnectionSocketFactory = sslConnectionSocketFactory;
        return this;
    }

    public void setConnectionRequestTimeout(int connectionRequestTimeout) {
        this.connectionRequestTimeout = connectionRequestTimeout;
    }

    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public void setSoTimeout(int soTimeout) {
        this.soTimeout = soTimeout;
    }

    public void setIdleConnTimeout(int idleConnTimeout) {
        this.idleConnTimeout = idleConnTimeout;
    }

    public void setCheckWaitTime(int checkWaitTime) {
        this.checkWaitTime = checkWaitTime;
    }

    public void setMaxConnPerHost(int maxConnPerHost) {
        this.maxConnPerHost = maxConnPerHost;
    }

    public void setMaxTotalConn(int maxTotalConn) {
        this.maxTotalConn = maxTotalConn;
    }

    public void setUserAgent(String userAgent) {
        this.userAgent = userAgent;
    }

    public IdleConnectionMonitorThread getIdleConnectionMonitorThread() {
        return this.idleConnectionMonitorThread;
    }

    private synchronized void prepare() {
        if (!this.prepared.get()) {
            Registry registry = RegistryBuilder.create().register("http", this.plainConnectionSocketFactory).register("https", this.sslConnectionSocketFactory).build();
            PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
            connectionManager.setMaxTotal(this.maxTotalConn);
            connectionManager.setDefaultMaxPerRoute(this.maxConnPerHost);
            connectionManager.setDefaultSocketConfig(SocketConfig.copy(SocketConfig.DEFAULT).setSoTimeout(this.soTimeout).build());
            this.idleConnectionMonitorThread = new IdleConnectionMonitorThread(connectionManager, this.idleConnTimeout, this.checkWaitTime);
            this.idleConnectionMonitorThread.setDaemon(true);
            this.idleConnectionMonitorThread.start();
            HttpClientBuilder httpClientBuilder = HttpClients.custom().setConnectionManager(connectionManager).setConnectionManagerShared(true).setSSLSocketFactory(this.buildSSLConnectionSocketFactory()).setDefaultRequestConfig(RequestConfig.custom().setSocketTimeout(this.soTimeout).setConnectTimeout(this.connectionTimeout).setConnectionRequestTimeout(this.connectionRequestTimeout).build()).setRetryHandler(this.httpRequestRetryHandler);
            if (StringUtils.isNotBlank(this.httpProxyHost) && StringUtils.isNotBlank(this.httpProxyUsername)) {
                CredentialsProvider provider = new BasicCredentialsProvider();
                provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort), new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword));
                httpClientBuilder.setDefaultCredentialsProvider(provider);
                httpClientBuilder.setProxy(new HttpHost(this.httpProxyHost, this.httpProxyPort));
            }

            if (StringUtils.isNotBlank(this.userAgent)) {
                httpClientBuilder.setUserAgent(this.userAgent);
            }

            this.closeableHttpClient = httpClientBuilder.build();
            this.prepared.set(true);
        }
    }

    private SSLConnectionSocketFactory buildSSLConnectionSocketFactory() {
        try {
            SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial((chain, authType) -> {
                return true;
            }).build();
            return new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, (String[])null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        } catch (KeyManagementException | KeyStoreException | NoSuchAlgorithmException e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    public CloseableHttpClient build() {
        if (!this.prepared.get()) {
            this.prepare();
        }

        return this.closeableHttpClient;
    }

    public static class IdleConnectionMonitorThread extends Thread {
        private final HttpClientConnectionManager connMgr;
        private final int idleConnTimeout;
        private final int checkWaitTime;
        private volatile boolean shutdown;

        public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr, int idleConnTimeout, int checkWaitTime) {
            super("IdleConnectionMonitorThread");
            this.connMgr = connMgr;
            this.idleConnTimeout = idleConnTimeout;
            this.checkWaitTime = checkWaitTime;
        }

        public void run() {
            while(true) {
                try {
                    if (!this.shutdown) {
                        synchronized(this) {
                            this.wait((long)this.checkWaitTime);
                            this.connMgr.closeExpiredConnections();
                            this.connMgr.closeIdleConnections((long)this.idleConnTimeout, TimeUnit.MILLISECONDS);
                            continue;
                        }
                    }
                } catch (InterruptedException e) {
                }

                return;
            }
        }

        public void trigger() {
            synchronized(this) {
                this.notifyAll();
            }
        }

        public void shutdown() {
            this.shutdown = true;
            synchronized(this) {
                this.notifyAll();
            }
        }
    }

    private static class SingletonHolder {
        private static final DefaultApacheHttpClientBuilder INSTANCE = new DefaultApacheHttpClientBuilder();

        private SingletonHolder() {
        }
    }
}
